
/*****************************************************************************\
 *                Test for BoxTree algo
\*****************************************************************************/

/*! @file
 *
 *  @brief
 *    Test for Boxtree algorithm.
 *
 *  Type './boxtree -h' for an explanation of the command line options
 *  and keys.
 *
 *  If you want an interactive test program, use 'interactive'.
 *
 *  Tests:
 *  -#  print Boxtree of plane
 *  -#  print Boxtree of sphere
 *  -#  all overlapping pairs of 2 coplanar, translated planes
 *  -#  all overlapping pairs of 2 touching spheres
 *
 *  @author Gabriel Zachmann
 *	- reimplemented by Weiyu Yi, wyi@tu-clausthal.de
 *
 *  @implementation
 *   This program is very similar to doptree.cpp!
 *   So if you change something here, chances are high that you should change
 *   the same thing in doptree, too.
 */


//---------------------------------------------------------------------------
//  Includes
//---------------------------------------------------------------------------

#include <Collision.h>
#include <ColBoxtree.h>
#include <ColDopTree.h>
#include <ColTopology.h>
#include <ColUtils.h>
#include "ColUtilsOpenGL.h"

using col::Point3;
using col::Point3f;
using col::ColGeometry;
using col::Primitive;
using col::Cube;
using col::Icosahedron;

using col::Vector3;
using col::Vector4;
using col::Matrix4;
using col::Boxtree;
using col::DopTree;

namespace
{

static const unsigned int MaxNumCol = 10000;

typedef enum
{
	PERSPECTIVE,
	ORTHOGRAPHIC	
} ProjectionE;

// here declaration of the global variables.
ColGeometry *geo[2];
col::Boxtree *boxtree[2];
vector<const std::vector<Point3f> *> points[2];
Matrix4 moving_matrix;

struct PolygonPairs
{
	unsigned int npairs;
	static const unsigned int MaxNum = 5000;
	unsigned int pair[MaxNum][2][3];
	int error;
	PolygonPairs() : npairs(0), error(0) {};
};

// render options
bool with_window = false;
ProjectionE proj = PERSPECTIVE;
GLenum fill_mode = GL_LINE;

GLdouble view_distance = 3.0;
col::Trackball tball;
Matrix4 viewing_matrix;
GLuint w, h;


GLdouble fixed_color[3] = { 1.0, 0.7, 0.7 };
GLdouble moving_color[3] = { 0.7, 0.7, 1.0 };

}

/***************************************************************************\
 *                        Test cases                                       *
\***************************************************************************/

/// return true, if index1 == index2, while order is not relevant.
bool trisEqual( const unsigned int *index1, unsigned int nindices1,
				const unsigned int *index2, unsigned int nindices2 )
{
	if ( nindices1 != nindices2 )
		return false;

	for ( unsigned int i = 0; i < nindices1; i ++ )
	{
		bool found = false;
		for ( unsigned int j = 0; j < i && !found; j ++ )
			if ( index1[i] == index2[j] )
				found = true;
		if ( ! found )
			return false;
	}

	return true;
}

bool print_pair( col::Data *data )
{
	PolygonPairs* pairs = static_cast<PolygonPairs*>(data->client_data);
	col::PolygonIntersectionData cdata = data->polisecdata.back();
	if ( ! pairs )
	{
		fputs("boxtree: print_pair: BUG: client_data = NULL!\n",stderr);
		exit(-1);
	}
	if ( pairs->npairs >= PolygonPairs::MaxNum )
	{
		fputs("boxtree: print_pair: too many pairs!\n",stderr);
		return false;
	}

	for ( unsigned int k = 0; k < 2; k ++ )
	{
		printf("( ");
		for ( unsigned int i = 0; i < cdata.nvertices[k]; i ++ )
			printf("%5u ", cdata.pgon[k][i] );
		printf(") ");
	}
	putchar('\n');

	// Check that each pair is reported only once.
	// If more than 4-gons occur, this tetst might produce a wrong answer.
	int err = pairs->error;
	for ( unsigned int i = 0; i < pairs->npairs; i ++ )
		if ( trisEqual(cdata.pgon[0], 3, pairs->pair[i][0], 3) &&
			 trisEqual(cdata.pgon[1], 3, pairs->pair[i][1], 3)  )
		{
			fputs("  this pair has already been reported!\n",stderr);
			pairs->error ++ ;
			break;
		}

	if ( err == pairs->error )
	{
		for ( unsigned int i = 0; i < 3; i ++ )
		{
			pairs->pair[pairs->npairs][0][i] = cdata.pgon[0][i];
			pairs->pair[pairs->npairs][1][i] = cdata.pgon[1][i];
		}
		pairs->npairs ++ ;
	}

	return false;							// keep coll det going
}

/// phase *must* start from 0 and increase step-wise by 1!
void testcase( int *phase )
{
try {
	int	result, result_wanted=-1;

	printf("\nphase %d\n", *phase );
	switch ( *phase )
	{
		case 0:
		{
			if ( geo[0] != NULL ) { delete geo[0]; geo[0] = NULL; }
			if ( geo[1] != NULL ) { delete geo[1]; geo[1] = NULL; }

			geo[0] = new ColGeometry();
            std::string filepath( FILE_LOAD_PATH );
            std::string filename( "boxtree/boxtree_plane_case_0.obj" );
			col::loadGeometryFromFile( geo[0], ( filepath+filename ).c_str() );
			boxtree[0] = new col::Boxtree( geo[0], points[0] );
		
			puts("tree:");
			boxtree[0]->printTree( geo[0] );
			result_wanted = -1;					// don't care

			if ( boxtree[0] != NULL ) { delete boxtree[0]; boxtree[0] = NULL; }
			if ( boxtree[1] != NULL ) { delete boxtree[1]; boxtree[1] = NULL; }

			break;
		}
		case 1:
		{
			if ( geo[0] != NULL ) { delete geo[0]; geo[0] = NULL; }
			if ( geo[1] != NULL ) { delete geo[1]; geo[1] = NULL; }

			geo[0] = new ColGeometry();
            std::string filepath( FILE_LOAD_PATH );
            std::string filename( "boxtree/boxtree_sphere_case_1.obj" );
			col::loadGeometryFromFile( geo[0], ( filepath+filename ).c_str() );
			//col::loadGeometryFromFile( geo[0], "../../../coll/wrapper/wrapper_opengl/test/Data/boxtree/boxtree_sphere_case_1.obj" );
			boxtree[0] = new col::Boxtree( geo[0], points[0] );

			puts("tree:");
			boxtree[0]->printTree( geo[0] );
			result_wanted = -1;					// don't care

			if ( boxtree[0] != NULL ) { delete boxtree[0]; boxtree[0] = NULL; }
			if ( boxtree[1] != NULL ) { delete boxtree[1]; boxtree[1] = NULL; }

			break;
		}
		case 2:
		{
			if ( geo[0] != NULL ) { delete geo[0]; geo[0] = NULL; }
			if ( geo[1] != NULL ) { delete geo[1]; geo[1] = NULL; }

			geo[0] = new ColGeometry();
            std::string filepath( FILE_LOAD_PATH );
            std::string filename( "boxtree/boxtree_plane_case_2_3.obj" );
			col::loadGeometryFromFile( geo[0], ( filepath+filename ).c_str() );
			//col::loadGeometryFromFile( geo[0], "../../../coll/wrapper/wrapper_opengl/test/Data/boxtree/boxtree_plane_case_2_3.obj" );
			boxtree[0] = new col::Boxtree( geo[0], points[0] );

			geo[1] = new ColGeometry();
			*(geo[1]) = *(geo[0]);
			boxtree[1] = new col::Boxtree( geo[1], points[1] );

			Matrix4 m = Matrix4::identity();
			m[3][0] = 0.2;
			m[3][1] = 0.1;

			// m12 = m1->m2, so here m = m2->m1
			moving_matrix = inverse(m);// for render

			col::Data data( geo[0], geo[1] );
            data.m12 = m;


			//
			PolygonPairs pairs;
			data.intersect_fun = print_pair;
			data.client_data = & pairs;

			puts("pairs:");
			boxtree[0]->check( *boxtree[1], geo[0], geo[1], &data );

			result = pairs.npairs;
			printf("%u pairs.\n", result );

			if ( pairs.error )
			{
				printf("  there have been %d duplicate pairs!\n",
					   pairs.error );
				result ++ ;
			}
			result_wanted = 100;

			if ( boxtree[0] != NULL ) { delete boxtree[0]; boxtree[0] = NULL; }
			if ( boxtree[1] != NULL ) { delete boxtree[1]; boxtree[1] = NULL; }

			break;
		}
		case 3:
		{
			if ( geo[0] != NULL ) { delete geo[0]; geo[0] = NULL; }
			if ( geo[1] != NULL ) { delete geo[1]; geo[1] = NULL; }

			geo[0] = new ColGeometry();
            std::string filepath( FILE_LOAD_PATH );
            std::string filename( "boxtree/boxtree_plane_case_2_3.obj" );
			col::loadGeometryFromFile( geo[0], ( filepath+filename ).c_str() );
			//col::loadGeometryFromFile( geo[0], "../../../coll/wrapper/wrapper_opengl/test/Data/boxtree/boxtree_plane_case_2_3.obj" );
			boxtree[0] = new col::Boxtree( geo[0], points[0] );

			geo[1] = new ColGeometry();
			*(geo[1]) = *(geo[0]);
			boxtree[1] = new col::Boxtree( geo[1], points[1] );

			Matrix4 m;
			m.setCol( 0, Vector4( 0.425179, 0.339653, 0.838963, 0.000000 ) );
			m.setCol( 1, Vector4( 0.529859, 0.658084,-0.534953, 0.000000 ) );
			m.setCol( 2, Vector4(-0.733807, 0.671983, 0.099836, 0.000000 ) );
			m.setCol( 3, Vector4(-0.360000,-0.855000,-0.080000, 1.000000 ) );
			moving_matrix = inverse(m);

			col::Data data( geo[0], geo[1] );
			data.m12 = m;

			data.intersect_fun = print_pair;
			PolygonPairs pairs;
			data.client_data = & pairs;

			puts("pairs:");
			boxtree[0]->check( *boxtree[1], geo[0], geo[1], &data );

			result = pairs.npairs;
			printf("%u pairs.\n", result );

			if ( pairs.error )
			{
				printf("  there have been %d duplicate pairs!\n",
					   pairs.error );
				result ++ ;
			}
			result_wanted = 6;

			if ( boxtree[0] != NULL ) { delete boxtree[0]; boxtree[0] = NULL; }
			if ( boxtree[1] != NULL ) { delete boxtree[1]; boxtree[1] = NULL; }

			break;
		}
		case 4:
		{
			if ( geo[0] != NULL ) { delete geo[0]; geo[0] = NULL; }
			if ( geo[1] != NULL ) { delete geo[1]; geo[1] = NULL; }

			geo[0] = new ColGeometry();
            std::string filepath( FILE_LOAD_PATH );
            std::string filename( "boxtree/boxtree_torus_case_4.obj" );
			col::loadGeometryFromFile( geo[0], ( filepath+filename ).c_str() );
			//col::loadGeometryFromFile( geo[0], "../../../coll/wrapper/wrapper_opengl/test/Data/boxtree/boxtree_torus_case_4.obj" );
			boxtree[0] = new col::Boxtree( geo[0], points[0] );

			geo[1] = new ColGeometry();
			*(geo[1]) = *(geo[0]);
			boxtree[1] = new col::Boxtree( geo[1], points[1] );

			Matrix4 m = Matrix4::identity();
			m[3][0] =
			m[3][1] = 1.0/sqrtf(2);
			moving_matrix = inverse(m);

			col::Data data( geo[0], geo[1] );
			data.m12 = m;

			PolygonPairs pairs;
			data.intersect_fun = print_pair;
			data.client_data = & pairs;

			puts("pairs:");
			boxtree[0]->check( *boxtree[1], geo[0], geo[1], &data );

			result = pairs.npairs;
			printf("%u pairs.\n", result );

			if ( pairs.error )
			{
				printf("  there have been %d duplicate pairs!\n",
					   pairs.error );
				result ++ ;
			}
			result_wanted = 1855;

			if ( boxtree[0] != NULL ) { delete boxtree[0]; boxtree[0] = NULL; }
			if ( boxtree[1] != NULL ) { delete boxtree[1]; boxtree[1] = NULL; }

			break;
		}
		case 5:
		{
			if ( geo[0] != NULL ) { delete geo[0]; geo[0] = NULL; }
			if ( geo[1] != NULL ) { delete geo[1]; geo[1] = NULL; }

			geo[0] = new ColGeometry();
            std::string filepath( FILE_LOAD_PATH );
            std::string filename( "boxtree/boxtree_torus_case_5.obj" );
			col::loadGeometryFromFile( geo[0], ( filepath+filename ).c_str() );
			//col::loadGeometryFromFile( geo[0], "../../../coll/wrapper/wrapper_opengl/test/Data/boxtree/boxtree_torus_case_5.obj" );
			boxtree[0] = new col::Boxtree( geo[0], points[0] );

			geo[1] = new ColGeometry();
			*(geo[1]) = *(geo[0]);
			boxtree[1] = new col::Boxtree( geo[1], points[1] );

			Matrix4 m = Matrix4::identity();
			m[3][0] =
			m[3][1] = 1.0/sqrtf(2);
			moving_matrix = inverse(m);

			col::Data data( geo[0], geo[1] );
			data.m12 = m;

			PolygonPairs pairs;
			data.intersect_fun = print_pair;
			data.client_data = & pairs;

			puts("pairs:");
			boxtree[0]->check( *boxtree[1], geo[0], geo[1], &data );

			result = pairs.npairs;
			printf("%u pairs.\n", result );

			if ( pairs.error )
			{
				printf("  there have been %d duplicate pairs!\n",
					   pairs.error );
				result ++ ;
			}
			result_wanted = 1855;

			if ( boxtree[0] != NULL ) { delete boxtree[0]; boxtree[0] = NULL; }
			if ( boxtree[1] != NULL ) { delete boxtree[1]; boxtree[1] = NULL; }

			break;
		}
        case 6:
        {
            /*Under investigation: These two objects should overlap but do not with
            the boxtree. Seems to be a bug with the transformation from node[1] coord
            system into node[0] coord system.
            */

			if ( geo[0] != NULL ) { delete geo[0]; geo[0] = NULL; }
			if ( geo[1] != NULL ) { delete geo[1]; geo[1] = NULL; }

			geo[0] = new ColGeometry();
            std::string filepath( FILE_LOAD_PATH );
            std::string filename( "boxtree/boxtree_bug_case_6_geo_1.obj" );
			col::loadGeometryFromFile( geo[0], ( filepath+filename ).c_str() );
			//col::loadGeometryFromFile( geo[0], "../../../coll/wrapper/wrapper_opengl/test/Data/boxtree/boxtree_bug_case_6_geo_1.obj" );
			boxtree[0] = new col::Boxtree( geo[0], points[0] );

			geo[1] = new ColGeometry();
            std::string filepath1( FILE_LOAD_PATH );
            std::string filename1( "boxtree/boxtree_bug_case_6_geo_2.obj" );
			col::loadGeometryFromFile( geo[1], ( filepath1+filename1 ).c_str() );
			//col::loadGeometryFromFile( geo[1], "../../../coll/wrapper/wrapper_opengl/test/Data/boxtree/boxtree_bug_case_6_geo_2.obj" );
			boxtree[1] = new col::Boxtree( geo[1], points[1] );

			Matrix4 m = Matrix4::identity();
			m.setCol( 0, Vector4(-0.831872, 0.554114, 0.030811, 0.000000 ) );
			m.setCol( 1, Vector4( 0.553810, 0.825262, 0.110647, 0.000000 ) );
			m.setCol( 2, Vector4( 0.035882, 0.109106,-0.993377, 0.000000 ) );
			m.setCol( 3, Vector4( 0.216430, 0.308500, 1.406295, 1.000000 ) );
			moving_matrix = inverse(m);
			
			col::Data data( geo[0], geo[1] );
			data.m12 = m;

			data.intersect_fun = print_pair;
			PolygonPairs pairs;
			data.client_data = & pairs;

            boxtree[0]->check( *boxtree[1], geo[0], geo[1], &data );
            
            result = pairs.npairs;
			result_wanted = 1;					// don't care

			if ( boxtree[0] != NULL ) { delete boxtree[0]; boxtree[0] = NULL; }
			if ( boxtree[1] != NULL ) { delete boxtree[1]; boxtree[1] = NULL; }

			break;
        }

		default:
			// last phase has been reached, signal to caller
			printf( "last phase has been reached; starting again.\n" );
			*phase = -1;
	}
	(*phase) ++ ;

	if ( *phase )
	{
		if ( result_wanted < 0 )
			puts(".. done");
		else
		{
			printf("result = %d\n", result );
			if ( result_wanted == result )
				puts(".. ok");
			else
				puts(".. failed");
		}
	} // else: we just had a wrap-around, i.e., we didn't really do a test
}
catch ( col::XCollision &x )
{
	x.print(stderr);
	exit(-1);					// not sensible to continue testing
}
}

void init(void) 
{
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glShadeModel(GL_FLAT);
	glEnable(GL_DEPTH_TEST);
}

void reshape (int wide, int height)
{
	w = wide;
	h = height;
	glViewport(0, 0, (GLsizei) w, (GLsizei) h);
	tball.setViewport( w, h );
}

void display(void)
{
	// all geometries are ready to render
	// begin display
	
	// trackball 
	if ( tball.getTrackMode() == col::TRACK_ROTATE )
	{
		REAL angle;
		REAL axis[3];
		tball.getRotation( axis, angle );
		Matrix4 m = Matrix4::rotation( angle, col::Vector3( axis[0], axis[1], axis[2] ) );
		viewing_matrix = m * viewing_matrix;
	}
	else
	if ( tball.getTrackMode() == col::TRACK_ZOOM )
	{
		REAL zoom;
		tball.getZoom( zoom );
		Matrix4 m = Matrix4::scale( col::Vector3( zoom+1 ) );
		viewing_matrix = m * viewing_matrix;
	}
	
	// 1) Prjection Trasformation
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity ();

	GLdouble range=2.0;
	switch( proj )
	{
		case ORTHOGRAPHIC:
			// orthographic projection
			if (w <= h)
				glOrtho(-1*range, range, -1*range*(GLdouble)h/(GLdouble)w,
						range*(GLdouble)h/(GLdouble)w, 1.0, 5.0);
			else
				glOrtho(-1*range*(GLdouble)w/(GLdouble)h,
						range*(GLdouble)w/(GLdouble)h, -1*range, range, 1.0, 5.0);
			break;
		case PERSPECTIVE:
			// perspective projection
			gluPerspective(60, (GLdouble)w/(GLdouble)h, 1.0, 5.0);
			break;
		default:
			fprintf( stderr, "No such projection mode!\n" );
			break;
	}

	// 2) View Transformation
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	// parameter with "-" cause they are defined as eye->world, here is used as world-eye
	// care about the sequence of the commands
	
	// zoom out or zoom in
	glTranslated( 0.0, 0.0, -view_distance );

	GLdouble view_mat[16];
	for ( unsigned int i=0; i<4; i++ )
	for ( unsigned int j=0; j<4; j++ )
		view_mat[ i*4+j ] = viewing_matrix[i][j];
	glMultMatrixd( view_mat );

	// 3) Modelling Transformation
	// drawing
	if ( geo[0] != NULL )
	{
		glPushMatrix();
		col::drawGeometry( geo[0], fill_mode, fixed_color );
		glPopMatrix();
	}

	if ( geo[1] != NULL )
	{
		glPushMatrix();
		GLdouble mat[16];
		for ( unsigned int i=0; i<4; i++ )
			for ( unsigned int j=0; j<4; j++ )
				mat[ i*4+j ] = moving_matrix[i][j];
		glMultMatrixd( mat );

		col::drawGeometry( geo[1], fill_mode, moving_color );
		glPopMatrix();
	}

	//
	glutSwapBuffers();
}

void mouseClick( int button, int state, int x, int y )
{
	tball.mouseClick( button, state, x, y );
}

void mouseMoving( int x, int y )
{
	tball.mouseMoving( x, y );
	glutPostRedisplay();
}


void keyboard(unsigned char key, int x, int y)
{
	static int phase = 0;

	switch (key) {
		case 27:
			exit(0);
			break;
		case ' ':
			testcase( &phase );
			glutPostRedisplay();
			break;
		case 'p':
		case 'P':
			if ( proj == PERSPECTIVE )
				proj = ORTHOGRAPHIC;
			else
			if ( proj == ORTHOGRAPHIC )
				proj = PERSPECTIVE;

			glutPostRedisplay();
			break;
		case 'i':
		case 'I':
			if ( fill_mode == GL_LINE )
				fill_mode = GL_FILL;
			else
			if ( fill_mode == GL_FILL )
				fill_mode = GL_POINT;
			else
			if ( fill_mode == GL_POINT )
				fill_mode = GL_LINE;

			glutPostRedisplay();
			break;
		default:
			break;
	}
}

/***************************************************************************\
 *                              Main                                       *
\***************************************************************************/

void commandlineerror( char *cmd, char *parm )
{
	if ( cmd )
		fprintf(stderr, "Offending option: %s\n", cmd );
	if ( parm )
		fprintf(stderr, "with first parameter: %s\n", parm );

	fprintf(stderr, "\n\nUsage: boxtree options ...\n"
	"Options:\n"
	"-w          with window; press space to continue with next test phase\n"
	"-h          show help\n"
	"Keys: (interactive version)\n"
	"i           polygon mode (wireframe, dots, filled)\n"
	"space       go to the next test case\n"
	"ESC         quit\n"
	"\n");

	if ( cmd )
		exit(-1);				// problem occured
	else
		exit(0);				// was option -h
}


void parsecommandline( int argc, char *argv[] )
{
	/* valid option characters; last char MUST be 0 ! */
	char optionchar[] =   { 'h', 'w', '\0' };
	int musthaveparam[] = {  0 ,  0  };
	int mhp[256];
	int isopt[256];
	char optchar;


	unsigned int nopts = strlen(optionchar);
	if ( nopts > 50 )
	{
		fprintf(stderr, "\n\nparsecommandline: the option-chars string "
				"seems to be\nVERY long (%d bytes) !\n\n", nopts );
		exit(-1);
	}

	fill_n( isopt, 256, 0 );
	fill_n( mhp, 256, 0 );
	for ( unsigned int i = 0; i < nopts; i ++ )
	{
		if ( isopt[static_cast<int>(optionchar[i])] )
		{
			fprintf(stderr, "\n\nparsecommandline: Bug: option character '%c'"
					" is specified twice in the\n"
							"option character array !\n\n", optionchar[i] );
			exit(-1);
		}
		isopt[ static_cast<int>(optionchar[i]) ] = 1;
		mhp[ static_cast<int>(optionchar[i])] = musthaveparam[i];
	}

	++argv; --argc;
	while ( argc > 0 )
	{
		if ( argv[0][0] == '-' )
		{
			optchar = argv[0][1];

			if ( ! isopt[static_cast<int>(optchar)] )
			{
				fprintf(stderr, "\nIs not a valid command line option\n");
				commandlineerror( argv[0], NULL );
			}
			for ( int i = 0; i < mhp[static_cast<int>(optchar)]; i ++ )
				if ( ! argv[1+i] || argv[1+i][0] == '-' )
				{
					fprintf(stderr, "\nCommand line option -%c must "
							"have %d parameter(s)\n",
							argv[0][1], mhp[static_cast<int>(optchar)] );
					commandlineerror( argv[0], NULL );
					argv += 1 + i;
					argc -= 1 + i;
					continue;
				}

			switch ( optchar )
			{
				case 'h': commandlineerror( NULL, NULL);  break;
				case 'w': with_window = true; break;
				default: fprintf(stderr, "\nBug in parsecommandline !\n");
						 commandlineerror( argv[0], NULL );
			}

			argv += 1 + mhp[static_cast<int>(optchar)];
			argc -= 1 + mhp[static_cast<int>(optchar)];
		}
		else
		{
			/* command line arg doesn't start with '-' */
			fprintf(stderr, "\nThis is not a valid command line option\n");
			commandlineerror( argv[0], NULL );
			/* or, load file instead .. */
		}
	}
}

int main(int argc, char** argv)
{
	// parse command line options
	parsecommandline( argc, argv );

	// initialize global and static data
	DopTree::init();
	viewing_matrix = Matrix4::identity();

	// prepare glut
	glutInit( &argc, argv );
	glutInitDisplayMode ( GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB );
	glutInitWindowSize( 350, 350 ); 
	glutInitWindowPosition( 100, 100 );
	glutCreateWindow( argv[0] );
	init();

	// register glut callback
	glutDisplayFunc( display ); 
	glutReshapeFunc( reshape );
	glutMouseFunc( mouseClick );
	glutMotionFunc( mouseMoving );
	glutKeyboardFunc( keyboard );

	// run...
	if ( with_window )
		// render loop begins
		glutMainLoop();
	else
	{
		int phase = 0;
		do
		{
			testcase( &phase );
		}
		while ( phase != 0 );
	}

	return 0;
}
